require "rubygems"
require "date"
require "amadeusdsl/graph"
require "amadeusdsl/algorithms"

module Amadeusdsl
    module Parser
    
    # the Base Command Parser class
    class CommandParser
        attr_reader :result , :command

        def initialize
            @canprint = false
            @ignorefailure = false
        end
        
        def ignorefailure
          return @ignorefailure
        end
        
        def setignorefailure(i)
          @ignorefailure = i
        end

        def canprint
            return @canprint
        end 
    
        def setcanprint(b)
            @canprint = b
        end

       # the option syntax
        def Option(*p)
            if p.include?(:Print) then setcanprint(true) end
            if p.include?(:IgnoreFailure) then setabort(true) end
        end              

        # the execution method
        def execute
            @result =  Amadeusdsl::Connection.put(command)
        end

        def print
            raise 'print must be implemented for each command !'
        end

        private :command,:canprint
    end
    

    # The book Command Parser class
    class ConnectionParser  < CommandParser

        def initialize 
            # our command is 3 - 3 - 10
            @command = "C                "
        end

        # return the code of the airport defined by s
        def From (s)
            @command[1,3] = Amadeusdsl::Lookup.Airport(s)
            
            @_from = s.strip << " (" << @command[1,3] << ")"
            return self
        end
        
        def FromCode (s)
          @command[1,3] = s
          @_from = s
          return self
        end
        # return the code of the airport defined by s
        def To (s)
           @command[4,3] = Amadeusdsl::Lookup.Airport(s)
           @_to = s.to_s.strip << " ("<<@command[4,3]<<")" 
           return self
        end
        
        def ToCode (s)
          @command[4,3] = s
          @_to = s
          return self
        end


        # date
        def On(s)
            @command[7,16] = s 
            @_on = s
            return self
        end
        
        
        def _execute
          @result =  Amadeusdsl::Connection.put(command)
          h = Array.new
          @result.each do
            |conn|
            h << {
              "airline_code" => conn[0,3], 
              "flight" => conn[0,6], 
              "from" => @_from,
              "to" => @_to,
              "duration" => Time.at(conn[11,2].to_i * 3600 + conn[14,2].to_i * 60),
              "departure" => Time.utc(@_on[0,4].to_i,@_on[5,2].to_i,@_on[8,2].to_i,conn[6,2].to_i,conn[9,2].to_i)
              }

          end
          @result = h
        end
        
        #execute
        def execute
          # super.execute
          # super werkt niet zoals je denkt dat het werkt denk ik :)
          @result =  Amadeusdsl::Connection.put(command)
          h = Array.new
          @result.each do
            |conn|
            h << {
              "airline_code" => conn[0,3], 
              "airline_name" => Amadeusdsl::Lookup.AirlineName(conn[0,3]),
              "flight" => conn[0,6], 
              "from" => @_from,
              "to" => @_to,
              "duration" => Time.at(conn[11,2].to_i * 3600 + conn[14,2].to_i * 60),
              "departure" => Time.utc(@_on[0,4].to_i,@_on[5,2].to_i,@_on[8,2].to_i,conn[6,2].to_i,conn[9,2].to_i)
              }

          end
          @result = h
        end

        # format
        def print
        if(canprint == true) then
          puts "Connections from " << @_from << " to " << @_to << " on " << @_on << "."
          @result.each do
              |conn|
              puts "Connection"
              puts "  Airline        " << conn["airline_name"]
              puts "  Flight nr      " << conn["flight"]
              puts "  Departure      " << conn["departure"].strftime("%d-%B %H:%M")
              puts "  Duration       " << conn["duration"].strftime("%H:%M")
            end
        end
        end
    end
   
    # Passenger parser
    class PassengerParser < CommandParser
        def Gender(s)
            unless s.capitalize[0] = 'M' || s.capitalize[0] = 'F' then
                abort "Incorrect Gender given !!"
            end
            @gender = ""<<s[0]
            return self
        end

        def Name(s)
           if s.length > 15 then
                abort "Name should be limited to 15 characters"
            end
            @name = s
            while (15- (@name.length)) > 0 do
                @name << " "
            end
            return self
        end

        def SurName(s)
            if s.length > 20 then
                abort "SurName should be limited to 20 characters"
            end
            @surname = s
            while (20-(@surname.length)) > 0 do
                @surname << " "
            end
            return self
        end
        
        def execute
          @result = {"gender" => @gender, "name" => @name, "surname" => @surname}
          return @result
        end
        
        def print
            if canprint == true then 
              puts @name << " " << @surname << " (" << @gender << ")"
            end
        end
    end

    # The Hold parser class
    class HoldParser < CommandParser
      
        def initialize
          @passengers = []
        end
        
        def Route(route)
          @route = route
          return self
        end
        
        def On(s)
            s = s.to_s
            @date = Time.utc(s[0,4].to_i,s[5,2].to_i,s[8,2].to_i)
            return self
        end

        def Flight(s)
            if s.length > 6 then
                abort "Incorrect FligthNumber given !!"
            end 
            @flight = s
            return self
        end

        def Class(s)
            @class = "" << s[0]
            return self
        end
        
        def AddPass(p)
          @passengers << p
          return self
        end
        

        def command(date,flight,c,passenger)
          @command = "H" << date << flight << c << passenger["gender"] << passenger["name"] << passenger["surname"]
          return @command
        end

        def print
            if canprint == true then 
              puts "Succesfully booked codes: "
              @result.each do |r|
                puts r
              end
            end
        end

        def execute
          @result = []
          #pp @flight
          if(@route == nil)
            @route = [{"departure"=>@date,"flight"=>@flight,"class"=>@class}]
          end
          @route.each do |flight|
            @passengers.each do |passenger|
              temp =  Amadeusdsl::Connection.put(command(flight["departure"].strftime("%Y-%m-%d"),flight["flight"],flight["class"],passenger))[0]
              if(!ignorefailure && temp == "FN") 
                Amadeusdsl::Error::Abort temp, "FN" => "Not enough seats Available !!" 
              end
              @result << (temp[1,33])
            end
          end
          return @result
        end
        
        def result
          return @result
        end
    end
    
    class DestinationParser < CommandParser
      
      def From(s) 
        @city = Amadeusdsl::Lookup.Airport(s)
        return self
      end
      
      def FromCode(c)
        @city = c[0,3]
        return self
      end
      
      def command
        @command = "D" << @city
        return @command
      end
      
      def _execute
        res =  Amadeusdsl::Connection.put(command)
        
        if(!ignorefailure) 
          Amadeusdsl::Error::BaseError res 
        end
        
        @result = []
        res.each do |x| 
          @result << {"code" => x}
        end
        return @result
      end
      
      
      def execute
        res =  Amadeusdsl::Connection.put(command)
        
        if(!ignorefailure) 
          Amadeusdsl::Error::BaseError res 
        end
        
        @result = []
        res.each do |x| 
          name = Amadeusdsl::Lookup.AirportName(x)
          @result << {"code" => x, "name" => name.strip}
        end
        return @result
      end

      def print
        if canprint == true then
          @result.each {|x| puts "[" << x["code"] << "]    " << x["name"] }
        end
      end
      
    end
    


    class AvailableParser < CommandParser
      
      def On(s) 
        @on = s.to_s[0,10]
        return self
      end
      
      def Flight(s)
        @flight = s[0,6]
        return self
      end
      
      def Class(c)
        @class = "" << c[0,1]
        return self
      end

      def command
        @command = "S" << @on << @flight << @class
      end

      def execute
        @result =  Amadeusdsl::Connection.put(command)[0]
       
        if(!ignorefailure) 
          Amadeusdsl::Error::BaseError @result 
        end
        
        @result = {"code" => @result, "amount" => @result.to_s[0,3].to_i, "price" => @result.to_s[3,5].to_i}
        return @result
      end

      def print
          if canprint == true then  
            puts "There are " <<  @result["amount"].to_s << " seats available."
            puts "They are priced at " << @result["price"].to_s << "$."
            end
      end

    end
    

    class RouteParser < CommandParser

        def Flight(s)
            @fl = s
            return self
        end


        def print 
          if canprint == true then  
           puts "Flight number " << @fl << " flies from : " << @result["from"] << " to " << @result["to"]
           puts ""
          end
        end

        def execute
            @result = Amadeusdsl::Lookup.FlightNumber(@fl)
            Amadeusdsl::Error::Abort @result ,"FN" => "There's no such flight number"
            return @result
        end
    end

    class BetweenParser < CommandParser
      
      def From(c) 
        @from = Amadeusdsl::Lookup.Airport(c)
      end
      
      def To(c)
        @to = Amadeusdsl::Lookup.Airport(c)
      end
      
      def Between( a , b )
        @start = Date.parse(a)
        @end = Date.parse(b)
      end
      
      def execute
        @result = []
        day = @start
        while ((day <=> @end) <= 0)
         command = Amadeusdsl::Parser::ConnectionParser.new
         command.From(@from)
         command.To(@to)
         command.On(day.to_s)
         command.execute
         command.result.each { |x| @result << x}
         day = day.next_day
        end
      end
      
      def print
        if canprint == true then 
          puts ""
          puts "Connections from " << @from << " to " << @to << "."
          puts "The connections in the period #{@start} to #{@end} are : "
          @result.each do
              |conn|
              puts "Connection (#{conn["on"]})"
              puts "  Airline        " << conn["airline_name"]
              puts "  Flight nr      " << conn["flight"]
              puts "  Departure      " << conn["departure"].strftime("%d-%B %H:%M")
              puts "  Duration       " << conn["duration"].strftime("%H:%M")
            end
        end
      end
      
    end


    class DayParser < CommandParser

        def Flight(s)
            @fl = s
            return self
        end

        def print 
          if canprint == true then  
          puts ""
          puts "The flight " << @fl << " goes every : "
          @result["result"].each {|day| puts "  " << day}
          end
        end

        def execute
          @result = Amadeusdsl::Lookup.DaysOfWeek(@fl)
          Amadeusdsl::Error::Abort @result ,"FN" => "There's no such flight number"
          return @result
        end
    end


    class CancelParser < CommandParser
        
        def initialize
          @codes = []
        end
        
        def Code(codes)
          if codes.kind_of?(Array)
            @codes += codes
          else
            @codes << codes
          end
        end

        def print 
          if canprint == true then
            puts "Tried to cancel " << @codes.size.to_i << " bookings:"
            @result.each do |r|
              if(r["code"] == "S")
                puts "Booking " << r["booking"] << ": Cancellation succesful"
              else
                puts "Booking " << r["booking"] << ": Cancellation failed"
              end
            end
            
          end
        end

        def execute
          @result = []
          @codes.each do |code|
            r = Amadeusdsl::Connection.put("X" << code)[0].to_s
            if(!ignorefailure && r == "FN") 
              Amadeusdsl::Error::Abort r, "FN" => "Invalid booking code", "FA" => "Booking is more than 7 days old, cannot be canceled"  
            end
            @result << {"code" => r, "booking" => code}
          end
          return @result
        end
    end

    class QueryParser < CommandParser
      
        def initialize
          @codes = []  
        end
        
        def Code(codes)
            if codes.kind_of?(Array)
              @codes += codes
            else
              @codes << codes
            end
            return self
        end


        def print 
          if canprint == true then  
            @result.each do |r|
              if(!r["success"])
                puts "Booking " << r["code"] << ": Invalid Booking Code"
              else
                if (!r["confirmed"])
                  puts "Booking " << r["code"] << ": currently being held."
                else
                  puts "Booking " << r["code"] << ": has been confirmed."
                end
                puts "Booking details:"
                puts "  " << r["firstname"] << " " << r["surname"] << "(" << r["gender"] << ")"
                puts "  Class: " << r["class"]
                puts "  Flight: " << r["flight"]
                puts "  Take-off: " << r["datetime"].strftime("%d %B %Y %H:%M")
                puts "  Duration: " << r["duration"].strftime("%H:%M")
                puts "  Price: $" << r["price"].to_s
                puts ""
              end
            end
          end
        end

        def execute
          
          @result = []
          @codes.each do |code|
            r = Amadeusdsl::Connection.put("Q"<<code)[0].to_s
            
            if(!ignorefailure) 
              Amadeusdsl::Error::Error r , "FN" => "Invalid Booking Code" 
            end
              
            if(r == "FN")
              @result << {"success" => false, "code" => code}
            else
              @result << {
                "success" => true,
                "code" => code,
                "datetime" => Time.new(r[1,4].to_i,r[6,2].to_i,r[9,2].to_i,r[11,2].to_i,r[14,2].to_i),
                "duration" => Time.at((r[16,2].to_i) * 3600 + (r[19,2].to_i) *60),
                "class" => r[27,1],
                "flight" => r[21,6],
                "gender" => r[28,1],
                "firstname" => r[29,1],
                "surname" => r[44,20],
                "price" => r[64,5].to_i,
                "confirmed" => (r[0,1] == "H") ? false : true
              }
            end
            
          end
          
          return @result
        end

    end

    class BookParser < CommandParser
        
        def initialize
          @codes = []
        end
        
        def Code(codes)
            if codes.kind_of?(Array)
              @codes += codes
            else
              @codes << codes
            end
            return self
        end

        def print 
          if canprint == true then 
              puts "Tried to book " << @result.size.to_s << " bookings:"
              puts ""
              @result.each do |r|
                if(r["success"] == "S")
                  puts "Successfully booked " << r["code"] << ": "
                  puts "  " << r["firstname"] << " " << r["surname"] << "(" << r["gender"] << ")"
                  puts "  Class: " << r["class"]
                  puts "  Take-off: " << r["datetime"].strftime("%d %B %Y %H:%M")
                  puts "  Duration: " << r["duration"].strftime("%H:%M")
                  puts "  Price: $" << r["price"].to_s 
                  puts ""
                elsif(r["success"] == "FN")
                  puts "Failure: No seats available for booking: " << r["code"]
                elsif(r["success"] == "FA")
                  puts "Failure: Double booking for booking: " << r["code"]
                end
              end
          end
        end

        def execute
          
          @result = []
          @codes.each do |code|
            r = Amadeusdsl::Connection.put("B"<<code)[0].to_s
            
            if(!ignorefailure && r == "FN") 
              Amadeusdsl::Error::Abort r, "FN" => "No seat Available !!", "FA" => "Seat already booked"
            end
            
            if(r == "FN" || r == "FA")
              @result << {"code" => code, "success" => r}
            else
              @result << {
                "success" => "S",
                "code" => code,
                "datetime" => Time.new(r[1,4].to_i,r[6,2].to_i,r[9,2].to_i,r[11,2].to_i,r[14,2].to_i),
                "duration" => Time.at(r[16,2].to_i * 3600 + r[19,2].to_i * 60),
                "flight" => r[21,6],
                "class" => r[27,1],
                "gender" => r[28,1],
                "firstname" => r[29,1],
                "surname" => r[44,20],
                "price" => r[64,5].to_i
              }
            end
          end
          return @result
        end

    end


    class AirportParser < CommandParser

        def List(var)
            @sort = var
        end

        def print 
        if canprint == true then
            puts @result 
        end
        end

        def execute 
            temp = Amadeusdsl::Connection.put('P')
            Amadeusdsl::Error::BaseError @result
            @indexl = 0
            @indexr = 42
            if @sort == :Country then
                @indexl = 23
            elsif @sort == :City then
                @indexr = 22
                @indexl = 3
            elsif @sort == :Code then
                @indexl= 0
                @indexr= 2
            end
            @result  = temp.map{ |x| (x[@indexl..@indexr])} 
            @result.uniq!
        end
    end


    class AirlineParser < CommandParser

        def List(var)
            @sort = var
        end
        
        def print 
        if canprint == true then
            puts @result 
        end
        end

        def execute
            temp = Amadeusdsl::Connection.put('A')
            Amadeusdsl::Error::BaseError @result
            @indexl = 0
            @indexr = 32
            if @sort == :Name then
                @indexl = 3
            elsif @sort == :Code then
                @indexl= 0
                @indexr= 2
            end
            @result  = temp.map{ |x| (x[@indexl..@indexr])} 
            @result.uniq!
        end
    end
  
  
    class SearchParser < CommandParser
      
      #default values
      def initialize
        @within = 0
        @class = "Economy"
        @sort = "price"
        @via = []
        @seats = 1
      end
      
      #day we want the flight, format yyyy-mm-dd, required
      def On(s)
        @on = Time.utc(s[0,4].to_i,s[5,2].to_i,s[8,2].to_i)
      end
      
      #days within the specified date we want to search, optional, default value = 0
      def Within(s)
        @within = s.to_i
      end
      
      #find cheapest => sort by "price"
      #find shortest => sort by "duration"
      #find most direct => sort by "hops"
      def SortBy(s)
        @sort = s
      end
      
      #Start airport, required
      def From(s)
        @from = Amadeusdsl::Lookup.Airport(s)
      end
      
      #Destination airport, required
      def To(s)
        @to = Amadeusdsl::Lookup.Airport(s)
      end

      #Add a location through which you want to travel, optional, usage:
      #For example you want to go to from paris to london via brussels & berlin (in that sequence) call:
      #Via(Brussels)
      #Via(Berlin)
      def Via(s)
        @via << s
      end
      
      #Class you want to fly in, optional, default value = E
      def Class(s)
        @class = s
      end
      
      #The number of seats you want to book, optional, default value = 1
      def Seats(s)
        @seats = s.to_i
      end
      
      def execute
        graph = Amadeusdsl::Graph.build_airport_graph #build connection graph
    
        @result = {"value" => 0, "path" => []}
        
        via = @via
        via << @to
        
        via.each do |hop|
         route = Amadeusdsl::Algorithm::RouteFinder.new(@from,hop,@seats,@class,graph,@sort,@on,@within).run
         
         if(route == nil)
           Amadeusdsl::Error.BaseError("ERRNR")
         end
         
         @result["value"] += route["value"]
         @result["path"] += route["path"]
        end

        return @result["path"]
      end
      
      def print 
          if canprint == true then  
              puts "Found route:"
              i = 1
              total_price = 0
              @result["path"].each do |flight|
                puts "Flight " << i.to_s << ": " << flight["flight"] << " (" << Amadeusdsl::Lookup.AirlineName(flight["airline_code"]) << ")"
                puts "From: " << flight["from"] << " - To: " << flight["to"]
                puts "Departure at: " << flight["departure"].strftime("%d %B %Y %H:%M")
                puts "Duration: " << flight["duration"].strftime("%H:%M")
                puts "Price: $" << flight["price"].to_s
                puts "Available seats: " << flight["seats"].to_s
                puts ""
                total_price += flight["price"]
                i+=1
              end
              puts "Total flights: " << (i - 1).to_s
              puts "Total price: $" << total_price.to_s
              
              total_time = (@result["path"].last["departure"].to_i + @result["path"].last["duration"].to_i) - @result["path"].first["departure"].to_i

              mins  = total_time / 60
              hours = (mins.to_f / 60)
              hours = hours.ceil % 24
              mins = mins % 60
              days  = hours / 24
              puts "Total duration: " << days.to_i.to_s << " days, " << hours.to_i.to_s << " hours, " << mins.to_i.to_s << " minutes"
              puts ""
          end
      end
      
    end
    
    
    class SelectParser < CommandParser
      
      def initialize
        @display = []
        @return = []
        @data = Hash.new
        @question = "Select an option: "
      end
      
      
      def From(data) 
        @data = data
        return self
      end
      
      def Display(params)       
        if params.kind_of?(Array)
          @display += params
        else
          @display << params
        end
        return self
      end
      
      def Return(params)          
        if params.kind_of?(Array)
          @return += params
        else
          @return << params
        end
        return self
      end
      
      def Question(q)
        @question = q
        return self
      end
      
      def execute 
          i = 1
          puts @question
          @data.each do
            |x|
            string = "[#{i}]  "
            if (@display == [])
              string += x.to_s
            else
              @display.each {|p| string += x[p].to_s + "  "}
            end
            
            puts string
            i += 1
          end
          
          input = gets
          while (input.to_i < 0 || input.to_i > @data.length) 
                input = gets
          end
          @result = @data[input.to_i-1]
      end
      
      def result
          if (@return == [])
            return @result
          else if (@return.length == 1)
                r = @result[@return[0]]
                return r
             else
                r = Hash.new
                @return.each {|p| r[p] = @result[p]}
                return r
             end
          end
      end
      
      def print
        if (canprint == true) then
          puts "You have chosen the following option : " + result.to_s
        end
      end
      
    end
    
    
    class FastSearchParser < CommandParser
      
      def initialize 
        @class = "E"
        @sortby = "price"
                       
        
      end
      
      def Class(c)
        @class = c[0,1]
        return self
      end
      
      def On(s)
        @on = Time.utc(s[0,4].to_i,s[5,2].to_i,s[8,2].to_i)
        return self
      end 
      
      def execute
        
        
        
      end
    end
    
  #end module
  end
 #end module
end
        
